﻿#include "Functions.h"

bool Functions::CompareBytes(std::vector<unsigned char> &a, std::vector<unsigned char> &b)
{
    if (a == b)
    {
        return true;
    }

    if (a.empty() || b.empty() || a.size() != b.size())
    {
        return false;
    }

    for (int i = 0; i < a.size(); i++)
    {
        if (a[i] != b[i])
        {
            return false;
        }
    }

    return true;
}

std::string Functions::ConvertByteSize(double byteCount)
{
    std::string size = "0 Bytes";
    if (byteCount >= 1073741824.0)
    {
        size = std::string::Format("{0:##.##}", byteCount / 1073741824.0) + " GB";
    }
    else if (byteCount >= 1048576.0)
    {
        size = std::string::Format("{0:##.##}", byteCount / 1048576.0) + " MB";
    }
    else if (byteCount >= 1024.0)
    {
        size = std::string::Format("{0:##.##}", byteCount / 1024.0) + " KB";
    }
    else if (byteCount > 0 && byteCount < 1024.0)
    {
        size = std::to_string(byteCount) + " Bytes";
    }

    return size;
}

bool Functions::TryParse(const std::string &s, Point *&temp)
{
    temp = Point::Empty;

    if (StringHelper::isEmptyOrWhiteSpace(s))
    {
        return false;
    }

    std::vector<std::string> data = StringHelper::split(s, ',');
    if (data.size() <= 1)
    {
        return false;
    }

    int tempX;
    if (!int::TryParse(data[0], tempX))
    {
        return false;
    }

    int tempY;
    if (!int::TryParse(data[1], tempY))
    {
        return false;
    }

    temp = new Point(tempX, tempY);
    return true;
}

Point *Functions::Subtract(Point *p1, Point *p2)
{
    return new Point(p1->X - p2->X, p1->Y - p2->Y);
}

Point *Functions::Subtract(Point *p1, int x, int y)
{
    return new Point(p1->X - x, p1->Y - y);
}

Point *Functions::Add(Point *p1, Point *p2)
{
    return new Point(p1->X + p2->X, p1->Y + p2->Y);
}

Point *Functions::Add(Point *p1, int x, int y)
{
    return new Point(p1->X + x, p1->Y + y);
}

std::string Functions::PointToString(Point *p)
{
    return StringHelper::formatSimple("{0}, {1}", p->X, p->Y);
}

bool Functions::InRange(Point *a, Point *b, int i)
{
    return std::abs(a->X - b->X) <= i && std::abs(a->Y - b->Y) <= i;
}

bool Functions::FacingEachOther(MirDirection dirA, Point *pointA, MirDirection dirB, Point *pointB)
{
    if (dirA == DirectionFromPoint(pointA, pointB) && dirB == DirectionFromPoint(pointB, pointA))
    {
        return true;
    }

    return false;
}

std::string Functions::PrintTimeSpanFromSeconds(double secs, bool accurate)
{
    TimeSpan t = TimeSpan::FromSeconds(secs);
    std::string answer;
    if (t.TotalMinutes < 1.0)
    {
        answer = StringHelper::formatSimple("{0}s", t.Seconds);
    }
    else if (t.TotalHours < 1.0)
    {
        answer = accurate ? std::string::Format("{0}m {1:D2}s", t.Minutes, t.Seconds) : StringHelper::formatSimple("{0}m", t.Minutes);
    }
    else if (t.TotalDays < 1.0)
    {
        answer = accurate ? std::string::Format("{0}h {1:D2}m {2:D2}s", static_cast<int>(t.Hours), t.Minutes, t.Seconds) : std::string::Format("{0}h {1:D2}m", static_cast<int>(t.TotalHours), t.Minutes);
    }
    else // more than 1 day
    {
        answer = accurate ? std::string::Format("{0}d {1:D2}h {2:D2}m {3:D2}s", static_cast<int>(t.Days), static_cast<int>(t.Hours), t.Minutes, t.Seconds) : std::string::Format("{0}d {1}h {2:D2}m", static_cast<int>(t.TotalDays), static_cast<int>(t.Hours), t.Minutes);
    }

    return answer;
}

std::string Functions::PrintTimeSpanFromMilliSeconds(double milliSeconds)
{
    TimeSpan t = TimeSpan::FromMilliseconds(milliSeconds);
    std::string answer;
    if (t.TotalMinutes < 1.0)
    {
        answer = StringHelper::formatSimple("{0}.{1}s", t.Seconds, static_cast<Decimal>(t.Milliseconds / 100));
    }
    else if (t.TotalHours < 1.0)
    {
        answer = std::string::Format("{0}m {1:D2}s", t.TotalMinutes, t.Seconds);
    }
    else if (t.TotalDays < 1.0)
    {
        answer = std::string::Format("{0}h {1:D2}m {2:D2}s", static_cast<int>(t.TotalHours), t.Minutes, t.Seconds);
    }
    else
    {
        answer = std::string::Format("{0}d {1}h {2:D2}m {3:D2}s", static_cast<int>(t.Days), static_cast<int>(t.Hours), t.Minutes, t.Seconds);
    }

    return answer;
}

MirDirection Functions::PreviousDir(MirDirection d)
{
    switch (d)
    {
        case MirDirection::Up:
            return MirDirection::UpLeft;
        case MirDirection::UpRight:
            return MirDirection::Up;
        case MirDirection::Right:
            return MirDirection::UpRight;
        case MirDirection::DownRight:
            return MirDirection::Right;
        case MirDirection::Down:
            return MirDirection::DownRight;
        case MirDirection::DownLeft:
            return MirDirection::Down;
        case MirDirection::Left:
            return MirDirection::DownLeft;
        case MirDirection::UpLeft:
            return MirDirection::Left;
        default:
            return d;
    }
}

MirDirection Functions::NextDir(MirDirection d)
{
    switch (d)
    {
        case MirDirection::Up:
            return MirDirection::UpRight;
        case MirDirection::UpRight:
            return MirDirection::Right;
        case MirDirection::Right:
            return MirDirection::DownRight;
        case MirDirection::DownRight:
            return MirDirection::Down;
        case MirDirection::Down:
            return MirDirection::DownLeft;
        case MirDirection::DownLeft:
            return MirDirection::Left;
        case MirDirection::Left:
            return MirDirection::UpLeft;
        case MirDirection::UpLeft:
            return MirDirection::Up;
        default:
            return d;
    }
}

MirDirection Functions::DirectionFromPoint(Point *source, Point *dest)
{
    if (source->X < dest->X)
    {
        if (source->Y < dest->Y)
        {
            return MirDirection::DownRight;
        }
        if (source->Y > dest->Y)
        {
            return MirDirection::UpRight;
        }
        return MirDirection::Right;
    }

    if (source->X > dest->X)
    {
        if (source->Y < dest->Y)
        {
            return MirDirection::DownLeft;
        }
        if (source->Y > dest->Y)
        {
            return MirDirection::UpLeft;
        }
        return MirDirection::Left;
    }

    return source->Y < dest->Y ? MirDirection::Down : MirDirection::Up;
}

MirDirection Functions::ShiftDirection(MirDirection dir, int i)
{
    return static_cast<MirDirection>((static_cast<int>(dir) + i + 8) % 8);
}

Size *Functions::Add(Size *p1, Size *p2)
{
    return new Size(p1->Width + p2->Width, p1->Height + p2->Height);
}

Size *Functions::Add(Size *p1, int width, int height)
{
    return new Size(p1->Width + width, p1->Height + height);
}

Point *Functions::PointMove(Point *p, MirDirection d, int i)
{
    switch (d)
    {
        case MirDirection::Up:
            p->Offset(0, -i);
            break;
        case MirDirection::UpRight:
            p->Offset(i, -i);
            break;
        case MirDirection::Right:
            p->Offset(i, 0);
            break;
        case MirDirection::DownRight:
            p->Offset(i, i);
            break;
        case MirDirection::Down:
            p->Offset(0, i);
            break;
        case MirDirection::DownLeft:
            p->Offset(-i, i);
            break;
        case MirDirection::Left:
            p->Offset(-i, 0);
            break;
        case MirDirection::UpLeft:
            p->Offset(-i, -i);
            break;
    }
    return p;
}

Point *Functions::Left(Point *p, MirDirection d)
{
    switch (d)
    {
        case MirDirection::Up:
            p->Offset(-1, 0);
            break;
        case MirDirection::UpRight:
            p->Offset(-1, -1);
            break;
        case MirDirection::Right:
            p->Offset(0, -1);
            break;
        case MirDirection::DownRight:
            p->Offset(1, -1);
            break;
        case MirDirection::Down:
            p->Offset(1, 0);
            break;
        case MirDirection::DownLeft:
            p->Offset(1, 1);
            break;
        case MirDirection::Left:
            p->Offset(0, 1);
            break;
        case MirDirection::UpLeft:
            p->Offset(-1, 1);
            break;
    }
    return p;
}

Point *Functions::Right(Point *p, MirDirection d)
{
    switch (d)
    {
        case MirDirection::Up:
            p->Offset(1, 0);
            break;
        case MirDirection::UpRight:
            p->Offset(1, 1);
            break;
        case MirDirection::Right:
            p->Offset(0, 1);
            break;
        case MirDirection::DownRight:
            p->Offset(-1, 1);
            break;
        case MirDirection::Down:
            p->Offset(-1, 0);
            break;
        case MirDirection::DownLeft:
            p->Offset(-1, -1);
            break;
        case MirDirection::Left:
            p->Offset(0, -1);
            break;
        case MirDirection::UpLeft:
            p->Offset(1, -1);
            break;
    }
    return p;
}

int Functions::MaxDistance(Point *p1, Point *p2)
{
    return std::max(std::abs(p1->X - p2->X), std::abs(p1->Y - p2->Y));

}

MirDirection Functions::ReverseDirection(MirDirection dir)
{
    switch (dir)
    {
        case MirDirection::Up:
            return MirDirection::Down;
        case MirDirection::UpRight:
            return MirDirection::DownLeft;
        case MirDirection::Right:
            return MirDirection::Left;
        case MirDirection::DownRight:
            return MirDirection::UpLeft;
        case MirDirection::Down:
            return MirDirection::Up;
        case MirDirection::DownLeft:
            return MirDirection::UpRight;
        case MirDirection::Left:
            return MirDirection::Right;
        case MirDirection::UpLeft:
            return MirDirection::DownRight;
        default:
            return dir;
    }
}

ItemInfo *Functions::GetRealItem(ItemInfo *Origin, unsigned short Level, MirClass job, std::vector<ItemInfo*> &ItemList)
{
    if (Origin->ClassBased && Origin->LevelBased)
    {
        return GetClassAndLevelBasedItem(Origin, job, Level, ItemList);
    }
    if (Origin->ClassBased)
    {
        return GetClassBasedItem(Origin, job, ItemList);
    }
    if (Origin->LevelBased)
    {
        return GetLevelBasedItem(Origin, Level, ItemList);
    }
    return Origin;
}

ItemInfo *Functions::GetLevelBasedItem(ItemInfo *Origin, unsigned short level, std::vector<ItemInfo*> &ItemList)
{
    ItemInfo *output = Origin;
    for (int i = 0; i < ItemList.size(); i++)
    {
        ItemInfo *info = ItemList[i];
        if (StringHelper::startsWith(info->Name, Origin->Name))
        {
            if ((info->RequiredType == RequiredType::Level) && (info->RequiredAmount <= level) && (output->RequiredAmount < info->RequiredAmount) && (Origin->RequiredGender == info->RequiredGender))
            {
                output = info;
            }
        }
    }
    return output;
}

ItemInfo *Functions::GetClassBasedItem(ItemInfo *Origin, MirClass job, std::vector<ItemInfo*> &ItemList)
{
    for (int i = 0; i < ItemList.size(); i++)
    {
        ItemInfo *info = ItemList[i];
        if (StringHelper::startsWith(info->Name, Origin->Name))
        {
            if ((static_cast<unsigned char>(info->RequiredClass) == (1 << static_cast<unsigned char>(job))) && (Origin->RequiredGender == info->RequiredGender))
            {
                return info;
            }
        }
    }
    return Origin;
}

ItemInfo *Functions::GetClassAndLevelBasedItem(ItemInfo *Origin, MirClass job, unsigned short level, std::vector<ItemInfo*> &ItemList)
{
    ItemInfo *output = Origin;
    for (int i = 0; i < ItemList.size(); i++)
    {
        ItemInfo *info = ItemList[i];
        if (StringHelper::startsWith(info->Name, Origin->Name))
        {
            if (static_cast<unsigned char>(info->RequiredClass) == (1 << static_cast<unsigned char>(job)))
            {
                if ((info->RequiredType == RequiredType::Level) && (info->RequiredAmount <= level) && (output->RequiredAmount <= info->RequiredAmount) && (Origin->RequiredGender == info->RequiredGender))
                {
                    output = info;
                }
            }
        }
    }
    return output;
}

std::string Functions::StringOverLines(const std::string &line, int maxWordsPerLine, int maxLettersPerLine)
{
    std::string newString = "";

    std::vector<std::string> words = StringHelper::split(line, ' ');

    int lineLength = 0;

    for (int i = 0; i < words.size(); i++)
    {
        lineLength += words[i].length() + 1;

        newString += words[i] + " ";
        if (i > 0 && i % maxWordsPerLine == 0 && lineLength > maxLettersPerLine)
        {
            lineLength = 0;
            newString += "\r\n";
        }
    }

    return newString;
}

std::vector<std::vector<unsigned char>> Functions::SplitArray(std::vector<unsigned char> &value, int bufferLength)
{
    int countOfArray = value.size() / bufferLength;
    if (value.size() % bufferLength > 0)
    {
        countOfArray++;
    }
    for (int i = 0; i < countOfArray; i++)
    {
//C# TO C++ CONVERTER TODO TASK: C++ does not have an equivalent to the C# 'yield' keyword:
        yield return value.Skip(i * bufferLength).Take(bufferLength)->ToArray();
    }
}

std::vector<unsigned char> Functions::CombineArray(std::vector<std::vector<unsigned char>> &arrays)
{
    std::vector<unsigned char> rv = std::vector<unsigned char>(arrays.Sum([&] (std::any x)
    {
        return x->Length;
    }));
    int offset = 0;
    for (auto array : arrays)
    {
        System::Buffer::BlockCopy(array, 0, rv, offset, array.size());
        offset += array.size();
    }
    return rv;
}

std::any Functions::DeserializeFromBytes(std::vector<unsigned char> &bytes)
{
    auto formatter = new BinaryFormatter();
//C# TO C++ CONVERTER NOTE: The following 'using' block is replaced by its C++ equivalent:
//ORIGINAL LINE: using (var stream = new MemoryStream(bytes))
    {
        auto stream = MemoryStream(bytes);

        delete formatter;
        return formatter->Deserialize(stream);
    }

//C# TO C++ CONVERTER TODO TASK: A 'delete formatter' statement was not added since formatter was used in a 'return' or 'throw' statement.
}
